


#include"sl_auto_dif_float_test.h"


// 这里只实现反向传播自动计算微分的框架，具体的计算求导代码还得自己编写。

// 这个计算图还真不能硬套这里的 stl_graph 的函数。这两者差别太大了。这里的节点得自己定义父子节点。

// 目前，对于任意变量格式的自动微分比较有难度。还是先实现最简单的,浮点数运算的数据格式。

#include<math.h>
#include"stl_type.h"

typedef struct ___variable_float_
{
	char str[8];
	float operand[2]; // 0,1 分别是左操作数和右操作数
	float result;
	//有几个参数就有几个梯度，偏导数
	float grad[2];   // 对最终结果的梯度。
	float jacobi[2]; // 对当前结果的偏导数。


	bool is_calculateed;

	void (*calculate)(bool is_calculate, struct ___variable_float_* vf);
	//Vertex node;  // 负责构建图
	struct ___variable_float_* operand_var[2];  // 两个操作数节点的指针。

}varfloat;

void cn_mul(bool iscalculate, varfloat* vf)
{
	// 计算
	if (iscalculate) {
		vf->result = vf->operand[0] * vf->operand[1];
	}
	else
	{
		vf->jacobi[0] = vf->operand[1];
		vf->jacobi[1] = vf->operand[0];
	}
	vf->is_calculateed = true;
}
void cn_div(bool iscalculate, varfloat* vf)
{
	// 计算
	if (iscalculate) {
		vf->result = vf->operand[0] / vf->operand[1];
	}
	else
	{
		vf->jacobi[0] = 1.0f / vf->operand[1];
		vf->jacobi[1] = 
			-1.0f * vf->operand[0] / (vf->operand[1] * vf->operand[1]);
	}
	vf->is_calculateed = true;
}
void cn_add(bool iscalculate, varfloat* vf)
{
	// 计算
	if (iscalculate) {
		vf->result = vf->operand[0] + vf->operand[1];
	}
	else
	{
		vf->jacobi[0] = 1;
		vf->jacobi[1] = 1;
	}
	vf->is_calculateed = true;
}
void cn_sub(bool iscalculate, varfloat* vf)
{
	// 计算
	if (iscalculate) {
		vf->result = vf->operand[0] - vf->operand[1];
	}
	else
	{
		vf->jacobi[0] = 1;
		vf->jacobi[1] = -1;
	}
	vf->is_calculateed = true;
}
void cn_ln(bool iscalculate, varfloat* vf)
{
	// 计算
	if (iscalculate) {
		vf->result = logf(vf->operand[0]);
	}
	else
	{
		vf->jacobi[0] = 1.0f / vf->operand[0];
	}
	vf->is_calculateed = true;
}
void cn_sin(bool iscalculate, varfloat* vf)
{
	// 计算
	if (iscalculate) {
		vf->result = sinf(vf->operand[0]);
	}
	else
	{
		vf->jacobi[0] = cosf(vf->operand[0]);
	}
	vf->is_calculateed = true;
}
void cn_(bool iscalculate, varfloat* vf)
{
	// 计算
	if (iscalculate) {
		vf->result =vf->operand[0];
	}
	else
	{
		vf->jacobi[0] = 1;
	}
	vf->is_calculateed = true;
}

void print_var(varfloat* vf)
{
	printfs("\r\n %s : ,result %f, opr %f %f  par %f %f ,grad %f %f  ",
		vf->str, vf->result,
		vf->operand[0], vf->operand[1],
		vf->jacobi[0], vf->jacobi[1],
		vf->grad[0], vf->grad[1]);
}

// 清空计算标志位
void  calculate_result_init(varfloat* vf)
{
	// 如果操作数不空
	if (vf->operand_var[0] != NULL) {
		calculate_result_init(vf->operand_var[0]);
	}
	if (vf->operand_var[1] != NULL ) {
		calculate_result_init(vf->operand_var[1]);
	}
	vf->is_calculateed = false;
}
// 计算节点结果。
void calculate_result(varfloat* vf)
{
	print_var(vf);
	// 如果操作数不空，同时没有计算，先计算操作数
	if (vf->operand_var[0] != NULL) {
		if (vf->operand_var[0]->is_calculateed == false)
			calculate_result(vf->operand_var[0]);
		vf->operand[0] = vf->operand_var[0]->result;
	}
	if (vf->operand_var[1] != NULL ) {
		if (vf->operand_var[1]->is_calculateed == false)
			calculate_result(vf->operand_var[1]);
		vf->operand[1] = vf->operand_var[1]->result;
	}
	// 计算当前节点。
	vf->calculate(true, vf);
	print_var(vf);
}

// 清空计算标志位
void  calculate_jocabi_init(varfloat* vf)
{
	// 如果操作数不空
	if (vf->operand_var[0] != NULL) {
		calculate_result_init(vf->operand_var[0]);
	}
	if (vf->operand_var[1] != NULL) {
		calculate_result_init(vf->operand_var[1]);
	}
	vf->operand[0] = 0;
	vf->operand[1] = 0;
	vf->grad[0] = 0;
	vf->grad[1] = 0;

}
// 计算节点偏导数。
void calculate_jocabi(varfloat* vf, float parent_jocabi)
{
	vf->calculate(false, vf);
	vf->grad[0] += vf->jacobi[0] * parent_jocabi;
	vf->grad[1] += vf->jacobi[1] * parent_jocabi;

	// 如果操作数不空，回溯计算各个参数的梯度。
	if (vf->operand_var[0] != NULL ) {
		calculate_jocabi(vf->operand_var[0], vf->grad[0]);
	}
	if (vf->operand_var[1] != NULL) {
		calculate_jocabi(vf->operand_var[1], vf->grad[1]);
	}
}

// 参考例子: https://blog.csdn.net/BGoodHabit/article/details/109693895

// 测试成功！卧槽！居然这么简单。还以为得用图的结构，没想到完全没必要……
void test_auto_differ()
{
	varfloat x1 = { .str = "x1", .is_calculateed = false,.jacobi[0] = 0,.jacobi[1] = 0,.calculate = cn_,   .operand[0] = 2,.operand[1] = 0,.operand_var[0] = NULL,.operand_var[1] = NULL};
	varfloat x2 = { .str = "x2", .is_calculateed = false,.jacobi[0] = 0,.jacobi[1] = 0,.calculate = cn_,   .operand[0] = 5,.operand[1] = 0,.operand_var[0] = NULL,.operand_var[1] = NULL };
	varfloat v1 = { .str = "v1", .is_calculateed = false,.jacobi[0] = 0,.jacobi[1] = 0,.calculate = cn_ln, .operand[0] = 0,.operand[1] = 0,.operand_var[0] = &x1,.operand_var[1] = NULL };
	varfloat v2 = { .str = "v2", .is_calculateed = false,.jacobi[0] = 0,.jacobi[1] = 0,.calculate = cn_mul,.operand[0] = 0,.operand[1] = 0,.operand_var[0] = &x1,.operand_var[1] = &x2 };
	varfloat v3 = { .str = "v3",.is_calculateed = false,.jacobi[0] = 0,.jacobi[1] = 0,.calculate = cn_sin,.operand[0] = 0,.operand[1] = 0,.operand_var[0] = &x2,.operand_var[1] = NULL };
	varfloat v4 = { .str = "v4", .is_calculateed = false,.jacobi[0] = 0,.jacobi[1] = 0,.calculate = cn_add,.operand[0] = 0,.operand[1] = 0,.operand_var[0] = &v1,.operand_var[1] = &v2 };
	varfloat v5 = { .str = "v5",.is_calculateed = false,.jacobi[0] = 0,.jacobi[1] = 0,.calculate = cn_sub,.operand[0] = 0,.operand[1] = 0,.operand_var[0] = &v4,.operand_var[1] = &v3 };


	calculate_result_init(&v5);
	calculate_result(&v5);
	printfs(" \r\n  auto calculate: %f ", v5.result);

	calculate_jocabi_init(&v5);
	calculate_jocabi(&v5, 1);
	printfs(" \r\n   auto calculate jacobi : %f  %f ", x1.grad[0], x2.grad[0]);


}





